`timescale 1ns/100ps

`include "sim_glb.sv"

module tc;

localparam          CLK_PRD                 = 5;
localparam          DEPTH                   = 8;
localparam          DATA_BW                 = 8;
localparam          ADDR_BW                 = $clog2(DEPTH);    // address range [0:DEPTH-1]
localparam          CNT_BW                  = $clog2(DEPTH + 1);// counter range [0:DEPTH]

reg                                         rst_n;
reg                                         clk;

reg                                         wen;
reg                                         ren;

wire                                        fifo_wen;
wire                                        fifo_full;
wire                                        fifo_afull;

wire                                        fifo_ren;
wire                                        fifo_empty;
wire                                        fifo_aempty;

wire                [CNT_BW-1:0]            fifo_cnt;

reg                 [ADDR_BW-1:0]           cfg_fifo_afull_th;  // afull_th<fifo_cnt<=DEPTH is afull
reg                 [ADDR_BW-1:0]           cfg_fifo_aempty_th; // 0<=fifo_cnt<=aempty_th   is aempty
wire                                        err_fifo_ilf;       // fifo internal logic fault
wire                                        err_fifo_wof;       // fifo write overflow
wire                                        err_fifo_ruf;       // fifo read underflow

wire                [DATA_BW-1:0]           fifo_wdata;
wire                [DATA_BW-1:0]           fifo_rdata;

reg                 [DATA_BW-1:0]           gen_wdata;
reg                 [DATA_BW-1:0]           chk_rdata;
reg                                         err_flag;

wire                                        raw_fifo_ren;
wire                                        raw_fifo_empty;
wire                                        mem_wen;
wire                [ADDR_BW-1:0]           mem_waddr;
wire                                        mem_ren;
wire                [ADDR_BW-1:0]           mem_raddr;

reg                                         wen_1d;
reg                 [ADDR_BW-1:0]           waddr_1d;
reg                 [DATA_BW-1:0]           wdata_1d;
reg                 [DATA_BW-1:0]           mem_ary[DEPTH-1:0];
reg                                         ren_1d;
reg                 [ADDR_BW-1:0]           raddr_1d;
reg                 [DATA_BW-1:0]           q_1d;
reg                 [DATA_BW-1:0]           q_2d;
reg                 [DATA_BW-1:0]           q_3d;
wire                [DATA_BW-1:0]           mem_rdata;
wire                [DATA_BW-1:0]           mem_wdata;
wire                [3-1:0]                 mem_rdpl_ce;
wire                [2-1:0]                 prefetch_cnt;

initial begin:CRG
    rst_n=1'b0;
    clk=1'b0;

    fork
        rst_n=#100.5 1'b1;
        forever clk=#CLK_PRD ~clk;
    join
end

RGRS_MNG    rgrs;
initial begin:REGRESS
    rgrs = new("tc_tiny_sfifo", 2);

    rgrs.wait_chks_done(100_000_000);
end

initial begin:GEN_WEN
    integer             tx_num;
    integer             rnd;

    wen = 1'b0;

    cfg_fifo_afull_th  = DEPTH-2;  // afull_th<fifo_cnt<=DEPTH is afull
    cfg_fifo_aempty_th = 1; // 0<=fifo_cnt<=aempty_th   is aempty

    @(posedge rst_n);

    tx_num=0;
    @(posedge clk);
    while(tx_num<100_000) begin
        rnd = $urandom_range(1,100);

        if (rnd<=80) begin
            wen = `U_DLY 1'b1;
            @(posedge clk);
            while(fifo_full==1'b1) begin
//            while(fifo_afull==1'b1) begin
                @(posedge clk);
            end

            tx_num = tx_num + 1;
        end
        else begin
            wen = `U_DLY 1'b0;
            @(posedge clk);
        end
    end
    wen = `U_DLY 1'b0;

    rgrs.one_chk_done("wen num is done.");
end

initial begin:GEN_REN
    integer             rx_num;
    integer             rnd;

    ren = 1'b0;

    @(posedge rst_n);

    rx_num=0;
    @(posedge clk);
    while(rx_num<100_000) begin
        rnd = $urandom_range(1,100);

        if (rnd<=85) begin
            ren = `U_DLY 1'b1;
            @(posedge clk);
            while(fifo_empty==1'b1) begin
                @(posedge clk);
            end

            rx_num = rx_num + 1;
        end
        else begin
            ren = `U_DLY 1'b0;
            @(posedge clk);
        end
    end
    ren = `U_DLY 1'b0;

    rgrs.one_chk_done("ren num is done.");
end
assign fifo_wen = wen & (~fifo_full);
//assign fifo_wen = wen & (~fifo_afull);
assign fifo_ren = ren & (~fifo_empty);

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        gen_wdata <=`U_DLY 0;
    end else begin
        if (fifo_wen==1'b1) begin
            gen_wdata <=`U_DLY gen_wdata + 1'd1;
        end
    end
end
assign fifo_wdata = gen_wdata;

sfifo_ctrl #(
        .DEPTH                          (DEPTH                          ) 	// must DEPTH>=2
) u_sfifo_ctrl ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .fifo_wen                       (fifo_wen                       ),
        .fifo_full                      (fifo_full                      ),
        .fifo_afull                     (fifo_afull                     ),

        .fifo_ren                       (raw_fifo_ren                   ),
        .fifo_empty                     (raw_fifo_empty                 ),
        .fifo_aempty                    (fifo_aempty                    ),

        .fifo_cnt                       (fifo_cnt                       ),

        .mem_wen                        (mem_wen                        ),
        .mem_waddr                      (mem_waddr                      ),
        .mem_ren                        (mem_ren                        ),
        .mem_raddr                      (mem_raddr                      ),

        .cfg_fifo_afull_th              (cfg_fifo_afull_th              ),
        .cfg_fifo_aempty_th             (cfg_fifo_aempty_th             ),
        .err_fifo_ilf                   (err_fifo_ilf                   ),	// fifo internal logic fault
        .err_fifo_wof                   (err_fifo_wof                   ),	// fifo write overflow
        .err_fifo_ruf                   (raw_fifo_ruf                   )	// fifo read underflow
);

wire                                        mem_wrpl_ce;
assign mem_wrpl_ce = ((u_prefetch.G_STUFF.vld_d[0]==1'b1) && (mem_rdpl_ce[1]==1'b0)) ? 1'b0 : 1'b1; 

assign mem_wdata = fifo_wdata;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        wen_1d <= `U_DLY 1'b0;
        waddr_1d <= `U_DLY {ADDR_BW{1'b0}};
        wdata_1d <= `U_DLY {DATA_BW{1'b0}};
    end else if ((mem_wen==1'b1) || (mem_wrpl_ce==1'b1)) begin
        wen_1d <= `U_DLY mem_wen;
        waddr_1d <= `U_DLY mem_waddr;
        wdata_1d <= `U_DLY mem_wdata;
    end
end

always@(posedge clk) begin
    if ((wen_1d==1'b1) && ((mem_wen==1'b1) || (mem_wrpl_ce==1'b1))) begin
        mem_ary[waddr_1d] <=`U_DLY wdata_1d;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        ren_1d <= `U_DLY 1'b0;
        raddr_1d <= `U_DLY {ADDR_BW{1'b0}};
    end else begin
        if (mem_rdpl_ce[0]==1'b1) begin
            ren_1d <= `U_DLY mem_ren;
            raddr_1d <= `U_DLY mem_raddr;
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        q_2d <=`U_DLY {DATA_BW{1'b0}};
    end else begin
        if (mem_rdpl_ce[1]==1'b1 && ren_1d==1'b1) begin
            q_2d <=`U_DLY mem_ary[raddr_1d];
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        q_3d <=`U_DLY {DATA_BW{1'b0}};
    end else begin
        if (mem_rdpl_ce[2]==1'b1) begin
            q_3d <=`U_DLY q_2d;
        end
    end
end

assign mem_rdata = q_3d;

fifo_prefetch #(
        .DATA_BW                        (DATA_BW                        ),
        .RDPL_NUM                       (3                              ),	// read data pipeline number 
        .ARCH_MODE                      ("RDPL_STUFF"                   )
) u_prefetch ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

// raw fifo with read data latency > 0
        .raw_fifo_empty                 (raw_fifo_empty                 ),
        .raw_fifo_ren                   (raw_fifo_ren                   ),

// zero read latency fifo after prefetching
        .zrl_fifo_ren                   (fifo_ren                       ),
        .zrl_fifo_empty                 (fifo_empty                     ),
        .zrl_fifo_rdata                 (fifo_rdata                     ),
        .zrl_fifo_ruf                   (err_fifo_ruf                   ),	// read underflow

        .mem_rdpl_ce                    (mem_rdpl_ce                    ),
        .mem_rdata                      (mem_rdata                      ),

        .prefetch_cnt                   (prefetch_cnt                   )
);

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        chk_rdata <=`U_DLY 0;
        err_flag  <=`U_DLY 1'b0;
    end else begin
        if (fifo_ren==1'b1) begin
            chk_rdata <=`U_DLY fifo_rdata + 1'd1;

            if (chk_rdata!=fifo_rdata) begin
                $error("chk_rdata %h is not same to fifo_rdata %h", chk_rdata, fifo_rdata);
                err_flag <=`U_DLY 1'b1;
            end
        end
    end
end
endmodule

